/** 
* Copyright (C) 2008 by The Regents of the University of California 
* Redistribution of this file is permitted under the terms of the GNU 
* Public License (GPL). 
* 
* @author Junghoo "John" Cho <cho AT cs.ucla.edu> 
* @date 3/24/2008 
*/

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"

#include "BTreeIndex.h"
#include "BTreeNode.h"

using namespace std;
// external functions and variables for load file and sql command parsing 

extern FILE* sqlin;
int sqlparse(void);

RC SqlEngine::run(FILE* commandline){  
	fprintf(stdout, "Bruinbase> ");  
	// set the command line input and start parsing user input  
	sqlin = commandline;  
	sqlparse();		// sqlparse() is defined in SqlParser.tab.c generated from               
					// SqlParser.y by bison (bison is GNU equivalent of yacc)  
	return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond){  
	RecordFile rf;		
	RecordId   rid;		
	BTreeIndex idx;		// index table

	SelCond* keyEQ	= NULL;
	SelCond* keyNE	= NULL;  
	SelCond* keyMax = NULL;  
	SelCond* keyMin = NULL;  
	SelCond* valEQ	= NULL;
	SelCond* valNE	= NULL; 
	SelCond* valMax = NULL;  
	SelCond* valMin = NULL;  
	   
	bool hasNotEquals = false;    
	RC     rc;  
	int    key;       
	string value;  
	int    count = 0;  
	int    diff;  
	int	 idxState = 0;  
	
	// open the table file  
	if ((rc = rf.open(table + ".tbl", 'r')) < 0) {    
		fprintf(stderr, "Error: table %s does not exist\n", table.c_str());    
		return rc;  
	}    

	int temp;    
	int keyGE = 0;  
	int keyLE = 0;    
	for (unsigned i = 0; i < cond.size(); i++) {	
		temp = atoi(cond[i].value);	
		switch(cond[i].attr) {	
		case 1:		// Condition on Key		
			switch (cond[i].comp) {		
			case SelCond::EQ:			
				keyEQ = (SelCond*) &cond[i];			
				break;		
			case SelCond::NE:			
				hasNotEquals = true;			
				keyNE = (SelCond*) &cond[i];			
				break;		
			case SelCond::LE:			
				temp++;		
			case SelCond::LT:			
				if (keyMax == NULL || atoi(keyMax->value) < temp) {				
					keyMax = (SelCond*) &cond[i];				
					if(keyMax->comp == SelCond::LE) {	keyLE = 1;	} 
					else {	keyLE = 0;	}			
				}			
				break;		
			case SelCond::GE:			
				temp--;		
			case SelCond::GT:			
				if (keyMin == NULL || atoi(keyMin->value) > temp) {				
					keyMin = (SelCond*) &cond[i];				
					if(keyMin->comp == SelCond::GE) {	keyGE = 1;	}			
					else {	keyGE = 0;	}			
				}			
				break;		
			}		
			break;	
		case 2:		// Condition on Value		
			switch (cond[i].comp) {		
				case SelCond::EQ:			
					valEQ = (SelCond*) &cond[i];	break;		
				case SelCond::NE:			
					hasNotEquals = true;			
					valNE = (SelCond*) &cond[i];			
					break;		
				case SelCond::LE:			
					temp++;	
				case SelCond::LT:			
					if (valMax == NULL || atoi(valMax->value) < temp)				
						valMax = (SelCond*) &cond[i];			
					break;		
				case SelCond::GE:			
					temp--;		
				case SelCond::GT:			
					if (valMin == NULL || atoi(valMin->value) > temp)				
						valMin = (SelCond*) &cond[i];			
					break;		
				}		
				break;	
		}
	}    
	if ((idx.open(table + ".idx", 'r')) == 0) {	
		idxState = 1;	
		IndexCursor ic;	
	
		if (keyEQ || keyMin || keyMax || keyNE) {				
			int keyToFind;		
			int keyLow;		
			int keyHigh;		
			int keyNot;		
			if (keyEQ)			keyToFind = atoi(keyEQ->value);		
			else if(keyMin) {			
				keyLow = atoi(keyMin->value);			
				keyToFind = keyLow;		
			}		
			if(keyMax) {			
				keyHigh = atoi(keyMax->value);			
				if(! keyMin) {		keyToFind = keyHigh;	}		
			}		
			if(keyNE) {		keyNot = atoi(keyNE->value);	}		
	
			if(keyMax) 			
				if((rc = idx.locate(0, ic)) != 0) {	idx.close();   return rc;	}		
			else if(! keyNE)			
				if ((rc = idx.locate(keyToFind, ic)) != 0) {   idx.close();	  return rc;	}		
	
			while (idx.readForward(ic, key, rid) == 0 ) {		   						
				if(keyEQ && key != keyToFind)									continue;			
				if(keyMin && ((keyGE==1)?(key < keyLow):(key <= keyLow))) 		continue;			
				if(keyMax && ((keyLE==1)?(key > keyHigh):(key >= keyHigh)))		break;			
				if(keyNE && key == keyNot)										continue;			 
				if ((rc = rf.read(rid, key, value)) != 0) {				
					fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());				
					idx.close();				
					return rc;			
				}		    
				
				if (valEQ != NULL && value != valEQ->value)		continue;			
				if (valMax != NULL &&  value >= valMax->value)	continue;			
				if (valMin != NULL && value <= valMin->value)	continue;			
				if (valNE && value <= valNE->value)				continue;			
		
				count++;			
			
				switch (attr) {			
					case 1:  // key			  
						fprintf(stdout, "%d\n", key);						break;			
					case 2:  // value			  
						fprintf(stdout, "%s\n", value.c_str());				break;			
					case 3:  // *			  
						fprintf(stdout, "%d '%s'\n", key, value.c_str());	break;			
				}			
				
				if(keyEQ && key == keyToFind) break;			
				if(keyNE && key != keyNot) break;		
			}	
		}	
		if(keyEQ || keyMin || keyMax || keyNE) {		
			if(attr == 4) {		fprintf(stdout, "%d\n", count);		}	
		}	
		if(! (keyEQ || keyMin || keyMax)) {		
			idx.close();		
			goto scanTable;	
		}	
		idx.close();  
	}    
	if(! idxState) {	

scanTable:	
		rid.pid = rid.sid = 0;	
		count = 0;	
		while (rid < rf.endRid()) {		
			if ((rc = rf.read(rid, key, value)) < 0) {		  
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());		  
				goto doneSelect;		
			}	
			if (keyEQ != NULL && (int) key != atoi(keyEQ->value))		goto nextTuple;		
			if (keyMax != NULL && (int) key >= atoi(keyMax->value))		goto nextTuple;		
			if (keyMin != NULL && (int) key <= atoi(keyMin->value))		goto nextTuple;		
			if (keyNE && (int) key == atoi(keyNE->value))   goto nextTuple;		
			if (valEQ != NULL && value != valEQ->value)		goto nextTuple;		
			if (valMax != NULL &&  value >= valMax->value)	goto nextTuple;		
			if (valMin != NULL && value <= valMin->value)	goto nextTuple;		
			if (valNE && value == valNE->value) goto nextTuple;		
	
			count++;
		
			switch (attr) {		
				case 1:  // key		  
					fprintf(stdout, "%d\n", key);		  break;		
				case 2:  // value		  
					fprintf(stdout, "%s\n", value.c_str());		  break;		
				case 3:  // *		  
					fprintf(stdout, "%d '%s'\n", key, value.c_str());		  break;	
			}		
nextTuple:	
			++rid;	
		}	
		if (attr == 4) {	fprintf(stdout, "%d\n", count);		}	
		rc = 0;	

doneSelect:	
		rf.close();	
	}	
	return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index){	
	/* your code here */	
	RecordFile* r = new RecordFile(table + ".tbl", 'w');	
	BTreeIndex idx;	
	
	if(index) {	
		idx.open(table + ".idx", 'w');	
	}	
	ifstream ifs;	
	ifs.open(loadfile.c_str());	
	string buf;	
	while (ifs.good() && getline(ifs, buf, '\n')) {		
		int key;	    
		string s;		
		RecordId id;		
		if (parseLoadLine(buf, key, s) == 0) {		    
			if (r->append(key, s, id) == 0) {			
				//insert into the B+ tree				
				if(index) {	
					if( idx.insert(key, id) != 0) {						
						cout << "Error either when inserting or spliting nodes to .idx file!" << endl;						
						return RC_FILE_WRITE_FAILED;					
					}				
				}			
			} else {	return RC_INVALID_ATTRIBUTE;	}		 
		} else {	return RC_INVALID_ATTRIBUTE;	}	
	} 	
	idx.close();	
	r->close();	
	delete(r);	
	return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value){    
	const char *s;    
	char        c;    
	string::size_type loc;        
	// ignore beginning white spaces    
	c = *(s = line.c_str());    
	while (c == ' ' || c == '\t') { c = *++s; }    
	// get the integer key value    
	key = atoi(s);    
	// look for comma    
	s = strchr(s, ',');    
	if (s == NULL) {		
		cout << "s == NULL" << endl;		
		return RC_INVALID_FILE_FORMAT; 	
	}    
	// ignore white spaces    
	do { c = *++s; } while (c == ' ' || c == '\t');        
	// if there is nothing left, set the value to empty string    
	if (c == 0) {         
		value.erase();        
		return 0;    
	}    
	// is the value field delimited by ' or "?    
	if (c == '\'' || c == '"') { s++; } 
	else {   c = '\n';  }    
    
	value.assign(s);    
	loc = value.find(c, 0);    
	if (loc != string::npos) {	value.erase(loc); }    
	return 0;
}
